其他
基础为零?如何将 C++ 编译成 WebAssembly
作者| 张翰(门柳)
出品|阿里巴巴新零售淘系技术部
本文知识点提炼: 1、如何使用 Emscripten 把 C++ 编译成 wasm。 2、如何使用 wasi-sdk 把 C++ 编译成 wasm。 3、如何运行编译好的 wasm 包。
说是 C++ 其实本文用到的代码都是纯 C 的。
Hello World!
#include <stdio.h>
int main() {
printf("Hello World!\n");
return 0;
}
clang hello.c -O3 -o out/hello
▐ WebAssembly 的编译和运行流程
Emscripten 也支持编译成独立的 wasm 包(不含 JS),但是想要运行这个 wasm 包需要宿主环境给它注入很多基础的 API,而且这些 API 是非标准的。如果想在 JS 环境里运行独立 wasm 包的话,要用 JS 实现这些 API。
关于 wasi,推荐阅读《Standardizing WASI: A system interface to run WebAssembly outside the web》 https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/
▐ 使用 Emscripten 编译
emcc hello.c -O3 -o out/hello-emcc.wasm
emcc hello.c -O3 -o out/hello-emcc.js
wasm2wat out/hello-emcc.wasm -o out/hello-emcc.wat
代码比较短,但是生成出来的 wasm 文件有 2.1KB,js 文件 16KB,主要是因为 stdio.h 头文件里有很多依赖,在运行时是由 js 代码来实现的。用 wasm 做 io 本身也不是个好的用法。
node out/hello-emcc.js
▐ 使用 wasi-sdk 编译
clang hello.c -O3 -o out/hello-wasi.wasm
~/wasi-sdk-8.0/bin/clang --sysroot ~/wasi-sdk-8.0/share/wasi-sysroot hello.c -O3 -o out/hello-wasi.wasm
如果是 Mac 电脑,遇到安全提示,在【系统偏好设置】-【安全与隐私】-【通用】里,找到“允许以下位置下载的App”的配置,下方应该有提示信息,点击允许就可以了。
hello-wasi.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)
wasm2wat out/hello-wasi.wasm -o out/hello-wasi.wat
wasmtime out/hello-wasi.wasm
int fib (int n) {
if (n <= 0) return 0;
if (n <= 2) return 1;
return fib(n - 2) + fib(n - 1);
}
▐ 使用 Emscripten 编译
emcc fib.c -s EXPORTED_FUNCTIONS='["_fib"]' -O3 -o out/fib-emcc.wasm
编译 C/C++ 的时候函数名会默认加上 _ 前缀,所以导出的接口名是 _fib 而不是 fib 。
// 编译并实例化 wasm 模块,返回导出的接口
async function loadWebAssembly (filename, env) {
const filePath = path.resolve(__dirname, filename)
// 读入 wasm 文件的二进制代码
const buffer = fs.readFileSync(filePath)
// 将 wasm 包实例化并传入外部接口,因为没有外部依赖,不传 env 也可以的
const results = await WebAssembly.instantiate(buffer, {
env: Object.assign({
'__memory_base': 0,
'__table_base': 0,
memory: new WebAssembly.Memory({ initial: 256, maximum: 256 }),
table: new WebAssembly.Table({ initial: 0, maximum: 128, element: 'anyfunc' })
}, env)
})
// 返回实例化好之后的接口
if (results && results.instance) {
return results.instance.exports
}
}
loadWebAssembly('./out/fib-emcc.wasm').then(apis => {
console.log(apis._fib(13)) // 输出 233
})
▐ 使用 wasi-sdk 编译
~/wasi-sdk-8.0/bin/clang --sysroot ~/wasi-sdk-8.0/share/wasi-sysroot fib.c \
-nostartfiles -fvisibility=hidden -Wl,--no-entry,--export=fib \
-O3 -o out/fib-wasi.wasm
wasmtime out/fib-wasi.wasm --invoke fib 7
wasmtime out/fib-emcc.wasm --invoke _fib 13
简单分析
性能对比
接下来干什么
END